package org.zjvis.datascience.service.graph;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.nimbusds.jose.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zjvis.datascience.common.dto.user.UserDTO;
import org.zjvis.datascience.common.exception.DataScienceException;
import org.zjvis.datascience.common.graph.importer.ImporterCSV;
import org.zjvis.datascience.common.graph.importer.ImporterGML;
import org.zjvis.datascience.common.graph.model.DefaultNodeStyle;
import org.zjvis.datascience.common.graph.util.GraphUtil;
import org.zjvis.datascience.common.model.ApiResultCode;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.vo.graph.AttrVO;
import org.zjvis.datascience.common.vo.graph.LinkVO;
import org.zjvis.datascience.common.vo.graph.NodeVO;
import org.zjvis.datascience.service.MinioService;

import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description GraphFileService
 * @date 2021-12-29
 */
@Service
public class GraphFileService {

    @Autowired
    private MinioService minioService;

    public static String GRAPH_FILE_DIR = "graph-file";

    public JSONObject graphFile2Json(String fileName) throws Exception {
        UserDTO user = JwtUtil.getCurrentUserDTO();
        String userName = user.getName();
        return graphFile2Json(userName, fileName);
    }

    /**
     *
     * @param userName
     * @param fileName
     * @return
     * @throws Exception
     */
    public JSONObject graphFile2Json(String userName, String fileName) throws Exception {
        List<String> objectNames = minioService.listObjects(GRAPH_FILE_DIR, userName + "/");

        if (!objectNames.contains(fileName)) {
            throw new DataScienceException(ApiResultCode.DATA_NULL, ApiResultCode.DATA_NULL.getMessage());
        }
        InputStream in = minioService.getObject(GRAPH_FILE_DIR, userName + "/" + fileName);
        JSONArray nodeArray = new JSONArray();
        JSONArray linkArray = new JSONArray();
        if (fileName.endsWith(".json")) {
            String content = IOUtils.readInputStreamToString(in, StandardCharsets.UTF_8);
            JSONObject obj = JSONArray.parseObject(content);
            nodeArray = obj.getJSONArray("nodes");
            linkArray = obj.getJSONArray("links");
        } else if (fileName.endsWith(".csv")) {
            JSONObject content = ImporterCSV.parseCSV2JSON(in, null);
            JSONArray headArray = content.getJSONArray("head");
            List<String> heads = new ArrayList<>();
            for (int i = 0; i < headArray.size(); i++) {
                JSONObject head = headArray.getJSONObject(i);
                heads.add(head.getString("name"));
            }
            String loadType = GraphParser.checkHeads(heads);
            JSONArray data = content.getJSONArray("data");
            if (loadType.equals("node")) {
                nodeArray = data;
            } else if (loadType.equals("link")) {
                linkArray = data;
            }
        } else if (fileName.endsWith(".gml")) {
            ImporterGML importerGML = new ImporterGML();
            importerGML.execute(in);
            nodeArray = importerGML.getNodes();
            linkArray = importerGML.getLinks();
        } else {
            throw new DataScienceException(ApiResultCode.GRAPH_LOAD_FORMAT_ERROR, ApiResultCode.GRAPH_LOAD_FORMAT_ERROR.getMessage());
        }

        Parser parser = new Parser(0L);
        return parser.parse(nodeArray, linkArray);
    }

    public static class Parser {
        private DefaultNodeStyle defaultStyle;
        private Set<String> cidMap;
        private Map<String, String> colorHelper;
        private int colorPtr;
        private Map<String, NodeVO> nIdMap;
        private Long graphId;

        public Parser(Long graphId) {
            defaultStyle = new DefaultNodeStyle();
            cidMap = new HashSet<>();
            colorHelper = new HashMap<>();
            colorPtr = 0;
            nIdMap = new HashMap<>();
            this.graphId = graphId;
        }

        public JSONObject parse(JSONArray nodeArray, JSONArray linkArray) {
            JSONObject ret = new JSONObject();
            List<NodeVO> nodes = new ArrayList<>();
            List<LinkVO> links = new ArrayList<>();
            if (nodeArray != null && nodeArray.size() > 0) {
                nodes = nodeArray.stream().map(this::parseNode).collect(Collectors.toList());
            }
            if (linkArray != null && linkArray.size() > 0) {
                links = linkArray.stream().map(this::parseLink).collect(Collectors.toList());
            }
            ret.put("nodes", nodes);
            ret.put("links", links);
            return ret;
        }

        public NodeVO parseNode(Object o) {
            JSONObject nodeObj = o instanceof JSONObject ? (JSONObject) o : (JSONObject) JSONObject.toJSON(o);
            NodeVO node = new NodeVO();
            Integer id = nodeObj.getInteger("id");
            String label = nodeObj.getString("label");
            String cid = nodeObj.getString("categoryId");
            List<AttrVO> attrs = new ArrayList<>();
            JSONObject attrsObj = GraphParser.checkJSONObject(nodeObj, "attributes");
            if (cidMap.add(cid)) {
                colorHelper.put(cid, defaultStyle.getColorLoop().get(colorPtr++ % defaultStyle.getColorLoopSize()));
            }
            if (attrsObj != null) {
                for (String key : attrsObj.keySet()) {
                    Object val = attrsObj.get(key);
                    if (val instanceof BigDecimal) {
                        val = ((BigDecimal) val).doubleValue();
                    }
                    int type = val instanceof String ? 12 : 2;
                    attrs.add(
                            AttrVO.builder()
                                    .key(key)
                                    .value(val)
                                    .type(type)
                                    .build()
                    );
                }
            }
            node.setId(GraphUtil.aliasNodeId(id.toString(), graphId));
            node.setLabel(label != null ? label : "");
            node.setCategoryId(cid);
            node.setAttrs(attrs);
            node.setOrderId(id);
            JSONObject config = GraphParser.checkJSONObject(nodeObj, "config");
            if (config != null) {
                JSONObject style = config.getJSONObject("style");
                if (style == null) {
                    style = new JSONObject();
                    style.put("fill", colorHelper.get(cid));
                    style.put("stroke", defaultStyle.getStyle().getString("stroke"));
                } else if (style.get("fill") == null) {
                    style.put("fill", colorHelper.get(cid));
                }
                node.setStyle(style);

                JSONObject labelCfg = config.getJSONObject("labelCfg");
                node.setLabelCfg(labelCfg == null ? JSONObject.parseObject(defaultStyle.getLabelCfg()) : labelCfg);
                Integer size = config.getInteger("size");
                node.setSize(size == null ? defaultStyle.getSize() : size);
                node.setType(config.getString("type") == null ? defaultStyle.getDefaultType() : config.getString("type"));
                node.setX(config.getInteger("x"));
                node.setY(config.getInteger("y"));
            } else {
                JSONObject style = new JSONObject();
                style.put("fill", colorHelper.get(cid));
                style.put("stroke", defaultStyle.getStyle().getString("stroke"));
                node.setStyle(style);
                node.setLabelCfg(JSONObject.parseObject(defaultStyle.getLabelCfg()));
                node.setSize(defaultStyle.getSize());
            }
            nIdMap.put(node.getId(), node);
            return node;
        }

        public LinkVO parseLink(Object o) {
            JSONObject linkObj = o instanceof JSONObject ? (JSONObject) o : (JSONObject) JSONObject.toJSON(o);
            LinkVO link = new LinkVO();
            Integer id = linkObj.getInteger("id");
            String label = linkObj.getString("label");
            Integer source = linkObj.getInteger("source");
            Integer target = linkObj.getInteger("target");

            link.setSource(GraphUtil.aliasNodeId(source.toString(), graphId));
            link.setTarget(GraphUtil.aliasNodeId(target.toString(), graphId));
            NodeVO srcNode = nIdMap.get(link.getSource());
            NodeVO tarNode = nIdMap.get(link.getTarget());

            String srcCid = srcNode.getCategoryId();
            String tarCid = tarNode.getCategoryId();
            String eid = srcCid + "_" + tarCid;

            List<AttrVO> attrs = new ArrayList<>();
            JSONObject attrsObj = GraphParser.checkJSONObject(linkObj, "attributes");
            if (attrsObj != null) {
                for (String key : attrsObj.keySet()) {
                    Object val = attrsObj.get(key);
                    int type = val instanceof String ? 12 : 2;
                    attrs.add(
                            AttrVO.builder()
                                    .key(key)
                                    .value(val)
                                    .type(type)
                                    .build()
                    );
                }
            }
            link.setId(GraphUtil.aliasLinkId(id.toString(), graphId));
            link.setLabel(label != null ? label : "");
            link.setEdgeId(eid);
            link.setDirected(linkObj.getBoolean("directed") != null ? linkObj.getBoolean("directed") : false);
            link.setWeight(linkObj.getDouble("weight") != null ? linkObj.getDouble("weight") : 1.0);
            link.setAttrs(attrs);

            JSONObject config = GraphParser.checkJSONObject(linkObj, "config");
            if (config != null) {
                link.setStyle(config.getJSONObject("style") == null ? new JSONObject() : config.getJSONObject("style"));
                link.setLabelCfg(config.getJSONObject("labelCfg") == null ? new JSONObject() : config.getJSONObject("labelCfg"));
                link.setType(config.getString("type") == null ? "line" : config.getString("type"));
                link.setCurveOffset(config.getFloat("curveOffset"));
                link.setControlPoints(config.getJSONArray("controlPoints"));
            }
            link.setOrderId(id);
            return link;
        }
    }

}
